package edu.vt.cbil.permute;

import edu.vt.cbil.dataset.RawData;
import edu.vt.cbil.dataset.ResultData;

import java.util.*;
import java.util.logging.Logger;

/**
 * Created by SunMing on 2016/6/29.
 */
public class Permute {
    private ArrayList<RawData> candidateRawDatas=new ArrayList<>();
    private ArrayList<ResultData> permuteResultDatas=new ArrayList<>();
    private ArrayList<IdRegion> idRegionSet=new ArrayList<>();
    private ArrayList<CNARegion> CNARegionSet=new ArrayList<>();
    private ArrayList<Integer> lengthSet=new ArrayList<>();
    private ArrayList<Integer> lengthSet_copy;
    private ArrayList<Integer> uniqueLengthSet;
    private Parameters permutePara=new Parameters();
    private int sampleNum;
    private ArrayList<Double> UScore=new ArrayList<>();
    private ArrayList<Double> UScore_copy;
    private ArrayList<Double> PScore=new ArrayList<>();
    private int permuteProbeSize;
    private Logger m_log;

    public Permute(ArrayList<RawData> candidateRawDatas,ArrayList<ResultData> permuteResultDatas){
        this.candidateRawDatas=candidateRawDatas;
        this.permuteResultDatas=permuteResultDatas;
        sampleNum=candidateRawDatas.get(0).getData().length;

        m_log=Logger.getLogger("edu.vt.cbil.permute");
    }

    public void processing(){
        getCNAs();
        if(CNARegionSet.size()>=2)
            permuteDetection();
        else
            m_log.info("There is not enough CNA units to be permuted");
    }

    //get the CNA units
    public void getCNAs(){
        ArrayList<Integer> candidateIdSet=new ArrayList<>();
        ArrayList<IdRegion> tempIdRegionSet=new ArrayList<>();

        getCandidateId(candidateIdSet);
        getTempIdRegion(candidateIdSet, tempIdRegionSet);
        getIdRegion(tempIdRegionSet);
        regionMerge();
    }

    //get the ID number that is not 0
    public void getCandidateId(ArrayList<Integer> candidateIdSet){
        for(RawData tempRawData:candidateRawDatas){
            for(int i=0;i<tempRawData.getData().length;i++){
                if(tempRawData.getData()[i]!=0){
                    candidateIdSet.add(tempRawData.getId());
                    break;
                }
            }
        }
        //System.out.println("OK");
    }

    //get the continuous units whose length is not less than 6
    public void getTempIdRegion(ArrayList<Integer> candidateIdSet,ArrayList<IdRegion> tempIdRegionSet){
        int tempStart=candidateIdSet.get(0),tempEnd=candidateIdSet.get(0),tempId;
        for(int i=1;i<candidateIdSet.size();i++){
            tempId=candidateIdSet.get(i);
            if(tempId-tempEnd!=1){
                if((tempEnd-tempStart+1)>=permutePara.getMinCNALength()){
                    IdRegion tempIdRegion=new IdRegion();
                    tempIdRegion.setStart(tempStart);
                    tempIdRegion.setEnd(tempEnd);
                    tempIdRegionSet.add(tempIdRegion);
                }
                tempStart=tempId;
                tempEnd=tempId;
            }
            else{
                tempEnd=tempId;
            }
            //the last probe
            if((i==candidateIdSet.size()-1)&&((tempId-tempStart+1)>permutePara.getMinCNALength())){
                IdRegion tempIdRegion=new IdRegion();
                tempIdRegion.setStart(tempStart);
                tempIdRegion.setEnd(tempId);
                tempIdRegionSet.add(tempIdRegion);
            }
        }
        //System.out.println("OK");
    }

    //get the CNA regions based on the Pearson Correlation Coefficient
    public void getIdRegion(ArrayList<IdRegion> tempIdRegionSet){
        int tempStart=0,tempEnd=0,tempId=0;
        double peaCorCoe=0.0;
        for(IdRegion tempIdRegion:tempIdRegionSet){
            tempStart=tempIdRegion.getStart();
            tempEnd=tempIdRegion.getEnd();
            tempId=tempStart;
            while (tempId<tempEnd){
                peaCorCoe=getPeaCorCoe(tempId,tempId+1,candidateRawDatas);
                if(peaCorCoe<permutePara.getPccThreshold()){
                    if((tempId-tempStart+1)>=permutePara.getMinCNALength()){
                        IdRegion validIdRegion=new IdRegion();
                        validIdRegion.setStart(tempStart);
                        validIdRegion.setEnd(tempId);
                        idRegionSet.add(validIdRegion);
                    }
                    tempStart=tempId+1;
                }
                tempId++;
            }
            //the last probe of the region
            if ((tempId==tempEnd)&&((tempId-tempStart+1)>=permutePara.getMinCNALength())){
                IdRegion validIdRegion=new IdRegion();
                validIdRegion.setStart(tempStart);
                validIdRegion.setEnd(tempId);
                idRegionSet.add(validIdRegion);
            }
        }
        //System.out.println("OK");
    }

    //calculate the Pearson Correlation Coefficient
    public double getPeaCorCoe(int id1,int id2,ArrayList<RawData>  candidateRawDatas){
        double peaCorCoe=0.0;
        double mean1,mean2,std1,std2,cov;

        mean1=0.0;mean2=0.0;
        for(int i=0;i<sampleNum;i++){
            mean1+=candidateRawDatas.get(id1).getData()[i];
            mean2+=candidateRawDatas.get(id2).getData()[i];
        }
        mean1/=sampleNum;
        mean2/=sampleNum;

        std1=0.0;std2=0.0;cov=0.0;
        for (int i=0;i<sampleNum;i++){
            std1+=(candidateRawDatas.get(id1).getData()[i]-mean1)*(candidateRawDatas.get(id1).getData()[i]-mean1);
            std2+=(candidateRawDatas.get(id2).getData()[i]-mean2)*(candidateRawDatas.get(id2).getData()[i]-mean2);
            cov+=(candidateRawDatas.get(id1).getData()[i]-mean1)*(candidateRawDatas.get(id2).getData()[i]-mean2);
        }
        std1/=sampleNum;
        std2/=sampleNum;

        if (std1==0||std2==0)
            return 0;

        peaCorCoe=cov/((sampleNum-1)*Math.sqrt(std1)*Math.sqrt(std2));

        return peaCorCoe;
    }

    //organize the CNA units and merge them into CNARegionSet,and get lengthSet
    public void regionMerge(){
        int cnaId=0;
        int cnaLength=0;

        m_log.info("There are " + idRegionSet.size() + " CNA units:");
        m_log.info("cnaId" + "\t\t" + "start"+"\t\t"+"end"+"\t\t"+"length");

        for(IdRegion idRegion:idRegionSet){
            cnaLength=idRegion.getEnd() - idRegion.getStart() + 1;

            CNARegion tempCNARegion=new CNARegion();
            tempCNARegion.setCnaId(cnaId);
            tempCNARegion.setIdRegion(idRegion);
            tempCNARegion.setLength(cnaLength);

            CNARegionSet.add(tempCNARegion);
            lengthSet.add(cnaLength);
            permuteProbeSize+=cnaLength;
            m_log.info(cnaId+"\t\t"+idRegion.getStart()+"\t\t"+idRegion.getEnd()+"\t\t"+cnaLength);

            cnaId++;
        }
        lengthSet_copy=new ArrayList<>(lengthSet);
    }

    //Significant Detection
    public void permuteDetection(){

        //get the unique lengths of CNA units
        Set uniqueLength=new HashSet(lengthSet);
        uniqueLengthSet=new ArrayList(uniqueLength);
        Collections.sort(uniqueLengthSet);
        //ArrayList uniqueLengthSet_copy=new ArrayList(uniqueLengthSet);

        double[][] maxUScore=new double[permutePara.getPermuteNum()][uniqueLengthSet.size()];

        calUScore(UScore);
        UScore_copy=new ArrayList<>(UScore);
        boolean loopFlag=true;
        ArrayList<CNARegion> permuteCNARegions=new ArrayList(CNARegionSet);
        while (loopFlag==true){
            permute(permuteCNARegions, maxUScore);
            loopFlag=SCAExclude(maxUScore,permuteCNARegions);
            /*if (lengthSet_copy.size()<=1)
                loopFlag=false;*/
        }
        calPScore(PScore,maxUScore);
        getResultDatas();
    }

    //calculate the UScores of all the CNA units
    public void calUScore(ArrayList<Double> UScore){
        int start,end;
        int cnaLength;
        double tempUScore;

        for(IdRegion idRegion:idRegionSet){
            start=idRegion.getStart();
            end=idRegion.getEnd();
            cnaLength=end-start+1;
            tempUScore=0.0;
            for(int i=start;i<=end;i++){
                for (int j=0;j<sampleNum;j++){
                    tempUScore+=candidateRawDatas.get(i).getData()[j];
                }
            }
            tempUScore/=(cnaLength*sampleNum);
            UScore.add(tempUScore);
        }
        //System.out.println("OK");
    }

    //CNA units permute,, and generate the maxUScore
    public void permute(ArrayList<CNARegion> permuteCNARegions,double[][] maxUScore){
        //int[][] idIndex=new int[permuteMatrix.length][sampleNum];
        for(int i=0;i<permutePara.getPermuteNum();i++){
            //a permutation starts
            double[][] randomPermuteMatrix=new double[permuteProbeSize][sampleNum];
            int maxIndex;
            int index,len,cnaStart;

            //Generate sampleNum*permuteMatrix.length random numbers
            Random rn=new Random();
            for(int j=0;j<sampleNum;j++){
                maxIndex=permuteCNARegions.size();
                boolean[] bool=new boolean[maxIndex];
                int randomInt=0;
                int pStart=0;

                //permute within the every sample
                for(int k=0;k<permuteCNARegions.size();k++){

                    //generate random numbers that is not repeated
                    do {
                        randomInt=rn.nextInt(maxIndex);
                    }while (bool[randomInt]);

                    bool[randomInt]=true;
                    index=randomInt;
                    len=permuteCNARegions.get(index).getLength();
                    cnaStart=permuteCNARegions.get(index).getIdRegion().getStart();

                    //update randomPermuteMatrix according to indexs and permuteMatrix
                    for(int m=pStart;m<(pStart+len);m++){
                        randomPermuteMatrix[m][j]=candidateRawDatas.get(cnaStart+m-pStart).getData()[j];
                    }
                    pStart+=len;
                }
            }
            //System.out.println("OK");
            findMaxUScore(randomPermuteMatrix,maxUScore[i]);
        }
        //System.out.println("OK");
    }

    //find the maximum UScore
    public void findMaxUScore(double[][] randomPermuteMatrix,double[] maxUScoreAti){
        double tempUScore;
        int probeNum=randomPermuteMatrix.length;
        double[] probeSum=new double[probeNum];
        int i,j,k;
        int len;
        int endPos;

        //calculate the sum of every probe
        for(i=0;i<probeNum;i++){
            probeSum[i]=0;
            for(j=0;j<sampleNum;j++){
                probeSum[i]+=randomPermuteMatrix[i][j];
            }
        }

        //calculate the UScore of the first uniqueLength
        len=uniqueLengthSet.get(0);
        tempUScore=0.0;
        maxUScoreAti[0]=0.0;
        endPos=probeNum-len;
        double[] regionSum=new double[endPos];
        for (j=0;j<endPos;j++){
            for (k=j;k<j+len;k++){
                tempUScore+=probeSum[k];
            }
            regionSum[j]=tempUScore;
            tempUScore=tempUScore/(sampleNum*len);
            if(tempUScore>maxUScoreAti[0]){
                maxUScoreAti[0]=tempUScore;
            }
        }

        //calculate the UScores of the rest uniqueLengths
        for(i=1;i<uniqueLengthSet.size();i++){
            len=uniqueLengthSet.get(i);
            endPos=probeNum-len;
            tempUScore=0.0;
            maxUScoreAti[i]=0.0;
            for (j=0;j<endPos;j++){
                for (k=j+uniqueLengthSet.get(i-1);k<j+len;k++){
                    tempUScore=regionSum[j]+probeSum[k];
                }
                regionSum[j]=tempUScore;
                tempUScore=tempUScore/(sampleNum*len);
                if(tempUScore>maxUScoreAti[i]){
                    maxUScoreAti[i]=tempUScore;
                }
            }
        }
    }

    //exclude SCAs and update permuteCNARegions
    public boolean SCAExclude(double[][] maxUScore,ArrayList<CNARegion> permuteCNARegions){
        double sigValue;
        int index;
        boolean flag = false;
        int maxLen = Collections.max(uniqueLengthSet);//To make sure there are enough probes
        int permuteNum=permutePara.getPermuteNum();
        double sigValueThreshold=permutePara.getSigValueThreshold();

        int permuteSize=permuteCNARegions.size();
        ArrayList<Integer> tempLengthSet=new ArrayList<>(lengthSet_copy);

        //search the UScore of CNA units according to the length
        for (int i=0;i<permuteSize;i++){
            sigValue=0.0;
            index=uniqueLengthSet.indexOf(lengthSet_copy.get(i));

            //accumulate the maxUScores more than UScore
            for(int j=0;j<permuteNum;j++){
                if(maxUScore[j][index]>UScore.get(i)){
                    sigValue++;
                }
            }
            sigValue/=(permuteNum+1);


            if(sigValue<sigValueThreshold){
                flag=true;
                if(permuteSize-tempLengthSet.get(i)>maxLen)
                    permuteProbeSize-=tempLengthSet.get(i);
                permuteCNARegions.remove(i);
                UScore_copy.remove(i);
                lengthSet_copy.remove(i);
                permuteSize=permuteCNARegions.size();
            }

        }
        return flag;
    }

    //calculate the PScores of all the CNA units again
    public void calPScore(ArrayList<Double> PScore,double[][] maxUScore){
        double pValue;
        int index;
        for(int i=0;i<lengthSet.size();i++){
            pValue=0.0;
            index=uniqueLengthSet.indexOf(lengthSet.get(i));
            for (int j=0;j<permutePara.getPermuteNum();j++){
                if(maxUScore[j][index]>UScore.get(i)){
                    pValue++;
                }
            }
            pValue/=(permutePara.getPermuteNum()+1);
            PScore.add(pValue);
        }
    }

    //get ResultDatas and update the UScore,PScore and SCATag of the CNARegionSet
    public void getResultDatas(){
        double pScoreThreshold=permutePara.getSigValueThreshold();

        m_log.info("There are "+CNARegionSet.size()+" results:");
        m_log.info("cnaId"+"\t\t"+"start"+"\t\t"+"end"+"\t\t"+"length"+"\t\t"+"uValue"+"\t\t\t\t"+"pValue"+"\t\t\t\t"+"SCATag");
        //update the CNARegionSet
        for (int i=0;i<CNARegionSet.size();i++){
            CNARegion tempCNARegion=new CNARegion();
            tempCNARegion=CNARegionSet.get(i);
            tempCNARegion.setuValue(UScore.get(i));
            tempCNARegion.setpValue(PScore.get(i));
            tempCNARegion.setSCATag(0);
            if(PScore.get(i)<pScoreThreshold){
                tempCNARegion.setSCATag(1);
                ResultData tempResultData=new ResultData();
                tempResultData.setStartId(CNARegionSet.get(i).getIdRegion().getStart());
                tempResultData.setEndId(CNARegionSet.get(i).getIdRegion().getEnd());
                tempResultData.setLength(CNARegionSet.get(i).getLength());
                permuteResultDatas.add(tempResultData);
            }
            CNARegionSet.set(i,tempCNARegion);
            m_log.info(i+"\t\t"+tempCNARegion.getIdRegion().getStart()+"\t\t"+tempCNARegion.getIdRegion().getEnd()+"\t\t"
                        +tempCNARegion.getLength()+"\t\t"+tempCNARegion.getuValue()+"\t\t"+tempCNARegion.getpValue()+"\t\t"
                        +tempCNARegion.getSCATag());
        }
    }
}
